/**                   GNU GENERAL PUBLIC LICENSE
 *                       Version 3, 29 June 2007
 *
 * Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 * Everyone is permitted to copy and distribute verbatim copies
 * of this license document, but changing it is not allowed.
 * See License.txt file that comes with this distribution
 *
 *
 * Author: Vitaly German <vitaly.german@gmail.com>, (C) 2007-2008
 * 
 **/

#pragma once
#include <loki/MultiMethods.h>
#include <loki/Sequence.h>

//#include "result_executor.h"
#include "solver_env.hpp"


/// Requires:
/// 1. Dispatch routines
/// Node IDs
 

typedef Loki::StaticDispatcher<Executor, 
  Result, Loki::Seq<NumericResult, VarResult>::Type, false, 
  Result, Loki::Seq<NumericResult, VarResult>::Type, Result_aptr> Dispatcher;


template<class Node, class B >
class ConstraintBuilder2 {
public:
    typedef ConstraintBuilder2<Node, B> self;
private:
    template<class CB, class K2F >
    friend void RegConstraintBuilder2Fun(CB& cb, K2F & k2f) ;

    SolverEnv_aptr env;
    F_And_aptr root;
    Constraint_Handle handle;
    B & build;
    std::stack<Result_aptr> res_stack;
    
    ConstraintBuilder2(const ConstraintBuilder2 &o);
    self & operator=(const self & o);

public:

    ConstraintBuilder2(SolverEnv_aptr env1, B& b ):env(env1), build(b) {
        root = env->get_root_F_And();
    }
    F_And_aptr & operator()(Node& node) {
        DoBuild(node);
        return root;
    }

private:

    void DoBuild(Node & node) {
        nbt(node, build);
    }

    void make_plus(Node node);
    void make_minus(Node node);
    void make_uminus(Node node);
    void make_mult(Node node);
    void make_div(Node node);
    void make_gr(Node node);
    void make_greq(Node node);
    void make_eq (Node node);
    void make_default (Node node);
    
    void feel_handle(Constraint_Handle & h, const Result_aptr & r, int sign);
    void do_dispatch(Executor & executor);
    void do_feel_handle();
};


template<class Node, class B >
void ConstraintBuilder2<Node, B>::do_dispatch(Executor & executor) {
    assert (res_stack.size()>=2);
    ///  n1 op n2 --> n1 n2 op
    Result_aptr n2 = res_stack.top();
    res_stack.pop();
    Result_aptr n1 = res_stack.top();
    res_stack.pop();
    /// op
    res_stack.push(Dispatcher::Go(*n1, *n2, executor));
}



template<class Node, class B >
void ConstraintBuilder2<Node,B>::make_plus(Node node) {
    rfm_solver::debug("\n***make_plus");
    assert(node.children.size() == 2);
    Executor exec (new Plus);
    do_dispatch(exec);
}

template<class Node, class B >
void ConstraintBuilder2<Node,B>::make_minus(Node node) {
    rfm_solver::debug("\n***make_minus");

    assert(node.children.size() == 2);
    /// x - num
    Executor exec (new Minus);
    do_dispatch(exec);
}

template<class Node, class B >
void ConstraintBuilder2<Node,B>::make_uminus(Node node) {
    rfm_solver::debug("\n***make_uminus ");
    assert(res_stack.size() >= 1);
    /// multiply to -1
    Executor exec (new Mult);
    Result_aptr n1 = res_stack.top();
    res_stack.pop();
    Result_aptr n2 = Result::create(Numeric(-1));
    res_stack.push(
        Dispatcher::Go(*n1, *n2, exec)
    );
}

template<class Node, class B >
void ConstraintBuilder2<Node,B>::make_mult(Node node) {
    rfm_solver::debug("\n***make_mult");
    assert(node.children.size() == 2);
    /// x * num
    Executor exec (new Mult);
    do_dispatch(exec);
}

template<class Node, class B >
void ConstraintBuilder2<Node,B>::make_div(Node node) {
    rfm_solver::debug("\n***make_div");
    assert(node.children.size() == 2);
    /// x / num
    Executor exec (new Div);
    do_dispatch(exec);
}


// for constants and variables
template<class Node, class B >
void ConstraintBuilder2<Node,B>::make_default(Node node) {
    rfm_solver::debug("\n***make_default\nInput: ");
    prn(node);
    rfm_solver::debug("\n==end node===\n");
    assert(node.children.empty());
    /// add node to stack
    res_stack.push(Result::create(node, 1));
}

template<class Node, class B >
void ConstraintBuilder2<Node,B>::make_gr(Node node) {
    rfm_solver::debug("\n***make_gr");
    prn(node);
    handle = root->add_GE();
    do_feel_handle();
}

template<class Node, class B >
void ConstraintBuilder2<Node,B>::make_greq(Node node) {
    rfm_solver::debug("\n***make_greq");
    prn(node);
    handle = root->add_GEQ();
    do_feel_handle();
}

template<class Node, class B >
void ConstraintBuilder2<Node,B>::make_eq(Node node) {
    rfm_solver::debug("\n***make_eq");
    prn(node);
    handle = root->add_EQ();
    do_feel_handle();
}

template<class Node, class B >
void ConstraintBuilder2<Node, B>::do_feel_handle() {
    assert(res_stack.size()==2);
    /// n2 >(=) n1
    Result_aptr n1 = res_stack.top();
    res_stack.pop();
    Result_aptr n2 = res_stack.top();
    res_stack.pop();
    /// n2 - n1 >(=) 0
    feel_handle(handle, n1, -1);
    feel_handle(handle, n2, 1);
}

template<class Node, class B >
void ConstraintBuilder2<Node,B>::feel_handle(Constraint_Handle & h, const Result_aptr & r, int sign) {
    NumericResult_aptr num = boost::dynamic_pointer_cast<NumericResult> (r);

    if (num) {
        /// we need -1 because constants asummed to be in ringht side. In Omega it's not the case.
        h.update_const( -1*sign * (Numeric)*num );
        return;
    }
    VarResult_aptr var = boost::dynamic_pointer_cast<VarResult> (r);
    assert(var);
    /// same as prev.
    h.update_const(-1*sign * var->const_val());
    const VarResult::VarTable & vt = var->var_val();
    typedef VarResult::VarTable::const_iterator CIT;
    for (CIT it = vt.begin(); it!=vt.end(); ++it) {
        Variable_ID var = env->get_variable(it->first);
        h.update_coef(var, sign * it->second);
    }
}

template<class CB, class K2F >
void RegConstraintBuilder2Fun(CB& cb, K2F & k2f) {
    rfm_solver::debug("\n***InitCBuilder");

    MarkManagerT & mrk = MarkManagerT::instance();
    k2f.RegisterFun(mrk.gr, boost::bind(&CB::make_gr, &cb, _1));
    k2f.RegisterFun(mrk.greq, boost::bind(&CB::make_greq, &cb, _1));
    k2f.RegisterFun(mrk.eq, boost::bind(&CB::make_eq, &cb, _1));

    k2f.RegisterFun(mrk.plus, boost::bind(&CB::make_plus, &cb, _1));
    k2f.RegisterFun(mrk.minus, boost::bind(&CB::make_minus, &cb, _1));
    k2f.RegisterFun(mrk.uminus, boost::bind(&CB::make_uminus, &cb, _1));
    k2f.RegisterFun(mrk.mult, boost::bind(&CB::make_mult, &cb, _1));
    k2f.RegisterFun(mrk.div, boost::bind(&CB::make_div, &cb, _1));

    k2f.RegisterFun(mrk.null, boost::bind(&CB::make_default, &cb, _1), true); // default
    k2f.stat();

    rfm_solver::debug("\n***InitCBuilder finished");
}
